/*================ client.c ======================*/ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <bits/sigaction.h>

#include "util.h"
#include "messages.h"
#include "dbs.h"
#include "client.h"
#include "comunication.h"

/*	Estados	*/
#define WP 0
#define WRR 1
/*	Eventos	*/
#define PACKET 0
#define RR 1
#define TIMER 2

#define PUT_FUNC 0
#define SYNCH_FUNC 1
#define GET_FUNC 2

typedef void (*ActionType)();

int strcasecmp(const char *s1, const char *s2);
char *strdup(const char *s1);
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
int sigfillset(sigset_t *set);


char* USERNAME;
char* PASSWORD;
char *send_pdu;
char *rcv_pdu;
char *message;
char *command;
char* server_aux[128];

int machine_state;
int machine_event;
int timeout_action;

int sync_server_count;
int son_server;
int tcp_sock;

struct sigaction my_action;

dbs SON = NULL;

/* If there is a server with <server_name>, returns it position in SERVER_LIST */
int check_server_list(char* server_name){
	int i;
	for(i = 0; SERVER_LIST[i] != NULL; i++){
		if(strcmp(SERVER_LIST[i]->name, server_name) == 0)
			return i;
	}
	return -1;
}

int processPdu(char *pdu){
	int i, op;
	op = pdu[1];
	for(i = 0; i < 128 && pdu[i+2] != '\0'; i++){
		message[i] = pdu[i+2];
	}
	return op;
}

void catch_alarm(int ignored){
	machine_event = TIMER;
}

void make_put(){
	char *locale, *number_string, *date;
	int number;
	memset(send_pdu, 0, 128);
	locale = strtok(NULL, " ");
	number_string = strtok(NULL, " ");
	date = strtok(NULL, " \n");
	if( (!locale) || (!number_string) || (!date) ){
		printf("%s\n", ERROR_PUT_FORMAT);
		/*DEVE AVISA DE ERRO NA FORMATACAO E PEDIR O COMANDO DE NOVO*/
	}
	number = atoi(number_string);
	send_pdu[0] = strlen(locale) + strlen(number_string) + strlen(date) + strlen(USERNAME) + strlen(PASSWORD) + 7;
	send_pdu[1] = 1;
	strcat(send_pdu, locale);
	strcat(send_pdu, " ");
	strcat(send_pdu, number_string);
	strcat(send_pdu, " ");
	strcat(send_pdu, date);
	strcat(send_pdu, " ");
	strcat(send_pdu, USERNAME);
	strcat(send_pdu, " ");
	strcat(send_pdu, PASSWORD);
}

int receive_synch_response(){
	int position, label_tmp, state_tmp;
	char *son_tmp, *name_tmp;
	label_tmp = atoi(strtok(message, " "));
	state_tmp = atoi(strtok(NULL, " "));
	son_tmp = strtok(NULL, " ");
	name_tmp = strtok(NULL, "\0");
	position = check_server_list(name_tmp);
	if(position != -1){
		if(state_tmp == 2 || state_tmp == 1){
			SON = SERVER_LIST[position];
			return 1;
		}
		SERVER_LIST[position]->label = label_tmp;
		SERVER_LIST[position]->state = state_tmp;
		SERVER_LIST[position]->son = strdup(son_tmp);
		return 0;
	}
	return -1;
}

int make_activation_synch(){
	int i, max_label = 0, max_position;
	for(i = 0; SERVER_LIST[i] != NULL; i++){
		if(SERVER_LIST[i]->label > max_label){
			max_label = SERVER_LIST[i]->label;
			max_position = i;
		}
	}
	if(max_label == 0){
		SON = SERVER_LIST[0];
		return 0;
	}else {
		SON = SERVER_LIST[max_position];
		return max_position;
	}
}

void make_son_synch(int server_position){
	send_pdu[0] = 3 + strlen((SERVER_LIST[server_position])->name);
	send_pdu[1] = 13;
	strcat(send_pdu, (SERVER_LIST[server_position])->name);
}

void wait_command(){
	char input[101];
	printf("%s\n", INSERT_COMMAND);
	fgets(input, 100, stdin);
	command = strtok(input, " \n");
	if(!strcasecmp(command, PUT)){
		make_put();
		memset(command, 0, strlen(command)+1);
	}
	if(!strcasecmp(command, SYNCH)){
		memset(command, 0, strlen(command)+1);
		memset(send_pdu, 0, 128);
		send_pdu[1] = 11;
	}
	if(!strcasecmp(command, GET)){
		char* parametros = strtok(NULL, "\n\0");
		if(!parametros){
			printf("Get de toda a BD\n");
			send_pdu[0] = 2;
			send_pdu[1] = 5;
		}
	}
	if(!strcasecmp(command, QUIT)){
		exit(0);
	}
}

/*	À espera de um pacote para enviar pela rede	*/
/*	Deve enviar pela rede o pacote 				*/
void PacketOverWP() {
	int i;
	switch(send_pdu[1]){
		case 1:
			bks_send_client_udp(send_pdu, SON->url, SON->port);
			memset(send_pdu, 0, 128);
			alarm(T_PUT_MAX);
			timeout_action = PUT_FUNC;
			machine_state = WRR;
			machine_event = RR;
			break;
		case 11:
			for(i = 0; SERVER_LIST[i] != NULL; i++){
				send_pdu[0] = 3 + strlen((SERVER_LIST[i])->name);
				send_pdu[1] = 11;
				strcat(send_pdu, (SERVER_LIST[i])->name);
				bks_send_client_udp(send_pdu, (SERVER_LIST[i])->url, (SERVER_LIST[i])->port);
				sync_server_count++;
				memset(send_pdu, 0, 128);
			}
			alarm(T_SYNCH_MAX);
			timeout_action = SYNCH_FUNC;
			memset(send_pdu, 0, 128);
			machine_state = WRR;
			machine_event = RR;
			break;
		case 12:
			son_server = make_activation_synch();
			bks_send_client_udp(send_pdu, (SERVER_LIST[son_server])->url, (SERVER_LIST[son_server])->port);
			memset(send_pdu, 0, 128);
			make_son_synch(son_server);
			printf("vamos sair do case 12\n");
			break;
		case 13:
			printf("vamos entrar no case 13\n");
			for(i = 0; SERVER_LIST[i] != NULL; i++){
				if(i != son_server){
					bks_send_client_udp(send_pdu, (SERVER_LIST[i])->url, (SERVER_LIST[i])->port);
				}
			}
			memset(send_pdu, 0, 128);
			printf("vamos sair do case 13\n");
			break;
		default:
			wait_command();
			break;
	}
}

/*	Á espera que chegue um pacote da rede	*/
/*	Deve receber esse pacote				*/
void RROverWRR() {
	int op;
	bks_rcv_udp(rcv_pdu);
	memset(message, 0, 128);
	op = processPdu(rcv_pdu);
	memset(rcv_pdu, 0, 128);
	printf("DEBUG de message: %s\n", message);
	switch(op){
		case 2:
			printf("%s\n", PUT_SUCESS);
			alarm(0);
			machine_state = WP;
			machine_event = PACKET;
			break;
		case 3:
			printf("recebeu novo SON -> %s\n", message);
			alarm(0);
			dbs new_son = find_dbs(SERVER_LIST, SERVER_LIST_SIZE, message);
			if(new_son){
				SON = new_son;
			}
			machine_state = WP;
			machine_event = PACKET;
			break;
		case 14:
			if(sync_server_count > 0){
				sync_server_count--;
				if(sync_server_count == 0){
					memset(send_pdu, 0, 128);
					send_pdu[0] = 2;
					send_pdu[1] = 12;
					machine_state = WP;
					machine_event = PACKET;
					break;
				}
			}
			break;
	}
}

void RROverWP() {}
void TimerOverWP() {}
void PacketOverWRR() {}
void TimerOverWRR() {
	switch(timeout_action){
		case PUT_FUNC:
			printf("Insucesso no put\n");
			break;
		case SYNCH_FUNC:
			printf("Insucesso no synch\n");
			break;
	}
	machine_state = WP;
	machine_event = PACKET;
}

ActionType ActionsTable[3][2] = {	{PacketOverWP	, PacketOverWRR	},
									{RROverWP		, RROverWRR		},
									{TimerOverWP	, TimerOverWRR	}};
 
void read_servers(){
	char *servers = readFile(SERVERS_FILE);
	char *server, *name, *url, *port_s;
	char* server_aux_list[SERVER_LIST_SIZE];
	int port, i = 0;
	server = strtok(servers, "\n");
	while(server != NULL){
		server_aux_list[i++] = server;
		server = strtok(NULL, "\n");
	}
	server_aux_list[i] = NULL;
	i = 0;
	while(server_aux_list[i] != NULL && SERVER_LIST_SIZE > i){
		name = strtok(server_aux_list[i], " \n\t");
		url = strtok(NULL, " \n\t");
		port_s = strtok(NULL, " \n\t");
		
		if(name == NULL || url == NULL || port_s == NULL)
			break;
		port = atoi(port_s);
		SERVER_LIST[i] = create_dbs(name, url, port);
		++i;
	}
}

void receive_packet(){

}

void send_packet(){

}

void fire_timer(){

}

void stop_timer(){

}

void Wait2ReceivePacket(){
	receive_packet();
	stop_timer();
}

void Wait2SendPacket(){
	send_packet();
	fire_timer();
}

ActionType camada_rede[2] = { Wait2SendPacket, Wait2ReceivePacket };

int main(int argc, char *argv[]){

	sync_server_count = 0;
	son_server = 0;
	send_pdu = (char *)malloc(128*sizeof(char));
	rcv_pdu = (char *)malloc(128*sizeof(char));
	message = (char *)malloc(128*sizeof(char));
	memset(send_pdu, 0, 128);
	memset(rcv_pdu, 0, 128);
	memset(message, 0, 128);
	if(argc != 3){
		printf("%s\n", INSERT_USER_PASS);
		exit(0);
	}	
	USERNAME = strdup(argv[1]);
	PASSWORD = strdup(argv[2]);
	read_servers();
	SON = SERVER_LIST[0];
	if(SON == NULL){
		printf("%s\n", ERROR_SERVRS_FILE);
				exit(0);
	}
	bks_init_udp();
	tcp_sock = bks_create_tcp();
	machine_state = 0;
	machine_event = 0;
	my_action.sa_handler = catch_alarm;
	if(sigfillset(&my_action.sa_mask) < 0)
		exit(0);
	my_action.sa_flags = 0;
	if(sigaction(SIGALRM, &my_action, 0) < 0)
		exit(0);
	while (1){
		ActionsTable[machine_event][machine_state]();
	}
	bks_close();
	return 0;

} 

